// ml278
// Copyright (C) 2008 Bjørn Øivind Bjørnsen, Aleksander Gjermundsen, Knut Andre Tidemann, Thomas Hansen
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#include "planetaryatmosphere.h"
#include "planetaryscenemanager.h"
#include "planetaryatmosphererenderable.h"
#include "planetarypage.h"
#include "planetaryoptions.h"
#include "planetaryplanet.h"

namespace Ogre
{
	PlanetaryAtmosphere::PlanetaryAtmosphere(PlanetarySceneManager *mgr)
	{
		mSceneMgr = mgr;
		mOptions = mgr->getOptions();
		mParent = NULL;
		mSceneNode = NULL;
		for(unsigned int i = 0; i < 6; i++)
			mRenderable[i] = NULL;

		//mZone = 0;
		mIsInit = false;
		mIsLoaded = false;
		
		mInAtmosphere = false;
		
		mKr = 0.0025f;
		mKm = 0.0010;
		mESun = 40.0f;
		mExposure = 2.0f;
		mWaveLengths = Vector3(0.650f,0.570f,0.475f);
		mScaleDepth = 0.25f;
	}

	PlanetaryAtmosphere::~PlanetaryAtmosphere()
	{
		if(mIsLoaded)
			unload();
		if(mIsInit)
			uninit();
	}

	void PlanetaryAtmosphere::init(PlanetaryPlanet *parent)
	{
		assert(!mIsInit);
		mParent = parent;
		
		// fix for some of the odd effects when geometry goes below the radius
		mInnerRadius = mOptions->mPlanetRadius + mParent->getMinAltitude();
		
		updateCalculations();

		// Create Scene Node
		if(!mSceneNode)
		{
			const String name = "Node::" + mOptions->mPlanetName + "Atmosphere";
			mSceneNode = mParent->getSceneNode()->createChildSceneNode(name);

			for(unsigned int i = 0; i < 6; i++)
			{
				mRenderableNode[i] = mSceneNode->createChildSceneNode(name + StringConverter::toString(i+1));
			}
		}

		// Create the zone renderable
		for(unsigned int i = 0; i < 6; i++)
		{
			mRenderable[i] = new PlanetaryAtmosphereRenderable(mSceneMgr);
			mRenderable[i]->init(i,this);
			mRenderable[i]->setVisible(false);
		}
		
		// The default materials are in the format "planetmaterial"_{ground,sky}From{Space,Atmosphere}
		MaterialManager *mgr = MaterialManager::getSingletonPtr();
		mGroundFromSpace =  mgr->getByName(mOptions->mPlanetMaterial + String("_groundFromSpace"));
		mGroundFromAtmosphere = mgr->getByName(mOptions->mPlanetMaterial + String("_groundFromAtmosphere"));
		mSkyFromSpace = mgr->getByName(mOptions->mPlanetMaterial + String("_skyFromSpace"));
		mSkyFromAtmosphere = mgr->getByName(mOptions->mPlanetMaterial + String("_skyFromAtmosphere"));
		
		if(mSkyFromSpace.isNull() || mGroundFromSpace.isNull() || mSkyFromAtmosphere.isNull() || mGroundFromAtmosphere.isNull())
		{
			throw(Ogre::Exception(1, "Cannot find atmosphere material " + mOptions->mPlanetMaterial, "PlanetaryAtmosphere::init",
						"FileNotFound", __FILE__, __LINE__));
		}
		
		mIsInit = true;
	}

	void PlanetaryAtmosphere::load()
	{
		assert(mIsInit);
		assert(!mIsLoaded);

		for(unsigned int i = 0; i < 6; i++)
		{
			mRenderable[i]->load();

			mRenderable[i]->setVisible(true);
			mRenderableNode[i]->attachObject(mRenderable[i]);
		}
	}

	void PlanetaryAtmosphere::unload()
	{
		for(unsigned int i = 0; i < 6; i++)
		{
			mRenderable[i]->unload();
		}
	}

	void PlanetaryAtmosphere::uninit()
	{
		assert(!mIsLoaded);
		assert(mIsInit);

		for(unsigned int i = 0; i < 6; i++)
		{
			if(mRenderable[i]->isVisible())
				mSceneNode->detachObject(mRenderable[i]);

			mRenderable[i]->uninit();

			delete mRenderable[i];
			mRenderable[i] = NULL;
		}

		mSceneNode->removeAndDestroyAllChildren();

		mIsInit = false;
	}

	void PlanetaryAtmosphere::setMaterial(String matname)
	{
		MaterialManager *mgr = MaterialManager::getSingletonPtr();
		mGroundFromSpace = mgr->getByName(matname + "_groundFromSpace");
		mGroundFromAtmosphere = mgr->getByName(matname + "_groundFromAtmosphere");
		mSkyFromSpace = mgr->getByName(matname + "_skyFromSpace");
		mSkyFromAtmosphere = mgr->getByName(matname + "_skyFromAtmosphere");
		
		if(mSkyFromSpace.isNull() || mGroundFromSpace.isNull() || mSkyFromAtmosphere.isNull() || mGroundFromAtmosphere.isNull())
		{
			throw(Ogre::Exception(1, "Cannot find atmosphere material " + mOptions->mPlanetMaterial, "PlanetaryAtmosphere::setMaterial",
			      "FileNotFound", __FILE__, __LINE__));
		}

		if(mInAtmosphere)
		{
			for(unsigned int i = 0; i < 6; i++)
				mRenderable[i]->setMaterial(mSkyFromAtmosphere->getName());
		}
		else
		{
			for(unsigned int i = 0; i < 6; i++)
				mRenderable[i]->setMaterial(mSkyFromSpace->getName());
		}
	}
	
	const PlanetaryAtmosphere::shaderParams PlanetaryAtmosphere::getShaderParams() const
	{
		shaderParams p;
		
		p.mInnerRadius = mInnerRadius;
		p.mOuterRadius = mOuterRadius;
		p.mOuterRadiusSqr = mOuterRadiusSqr;
		
		p.mScale = mScale;
		p.mScaleDepth = mScaleDepth;
		p.mScaleOverScaleDepth = mScaleOverScaleDepth;
		
		p.mWaveLengths = mWaveLengths;
		p.mInvWaveLengthsPow4 = mInvWaveLengthsPow4;
		p.mKr = mKr;
		p.mKm = mKm;
		p.mKr4PI = mKr4PI;
		p.mKm4PI = mKm4PI;
		p.mESun = mESun;
		p.mKrESun = mKrESun;
		p.mKmESun = mKmESun;
		p.mDepth = mDepth;
		p.mExposure = mExposure;
		
		return(p);
	}

	void PlanetaryAtmosphere::setShaderParams(PlanetaryAtmosphere::shaderParams & params)
	{
		mInnerRadius = params.mInnerRadius;
		mOuterRadius = params.mOuterRadius;
		mOuterRadiusSqr = params.mOuterRadiusSqr;
		
		mScale = params.mScale;
		mScaleDepth = params.mScaleDepth;
		mScaleOverScaleDepth = params.mScaleOverScaleDepth;
		
		mWaveLengths = params.mWaveLengths;
		mInvWaveLengthsPow4 = params.mInvWaveLengthsPow4;
		mKr = params.mKr;
		mKm = params.mKm;
		mKr4PI = params.mKr4PI;
		mKm4PI = params.mKm4PI;
		mESun = params.mESun;
		mKrESun = params.mKrESun;
		mKmESun = params.mKmESun;
		mDepth = params.mDepth;
		mExposure = params.mExposure;
	}

	/** updates the shader parametres
	 * @note THIS MUST BE CALLED *AFTER* CAMERA MOVEMENT!
	 */
	void PlanetaryAtmosphere::updateShader()
	{
		// TODO get sun position/direction from scenemanager

		if(!mIsInit)
			return;
		
		bool toggle = false;
		GpuProgramParametersSharedPtr pParams;
		Ogre::Pass *pPass;
		Ogre::MaterialPtr pGround;
		Ogre::MaterialPtr pSky;
		
		//Real sqrCameraHeight = getSquaredCameraHeight();
		Real sqrCameraHeight = mSceneMgr->getActiveCamera()->getRealPosition().squaredDistance(mParent->getPosition());
		if(sqrCameraHeight > mOuterRadiusSqr)
		{
			if(mInAtmosphere) 
			{
				mInAtmosphere = false;
				toggle = true;
			}
			
			// ground from space
			pGround = mGroundFromSpace;
			// sky from space
			pSky = mSkyFromSpace;
			// sky
			pPass = pSky->getTechnique(0)->getPass(0);
			pParams = pPass->getVertexProgramParameters();
			
			pParams->setNamedConstant("fCameraHeightSqr", sqrCameraHeight);
			pParams->setNamedConstant("fInnerRadius", mInnerRadius);
			pParams->setNamedConstant("fOuterRadius", mOuterRadius);
			pParams->setNamedConstant("fOuterRadiusSqr", mOuterRadiusSqr);
			pParams->setNamedConstant("fScale",mScale);
			pParams->setNamedConstant("fScaleOverScaleDepth",mScaleOverScaleDepth);
			
			pParams = pPass->getFragmentProgramParameters();

			pParams->setNamedConstant("fExposure",mExposure);
			
			// ground
			pPass = pGround->getTechnique(0)->getPass(1);
			pParams = pPass->getVertexProgramParameters();
			
			pParams->setNamedConstant("fCameraHeightSqr", sqrCameraHeight);
			pParams->setNamedConstant("fInnerRadius", mInnerRadius);
			pParams->setNamedConstant("fOuterRadius", mOuterRadius);
			pParams->setNamedConstant("fOuterRadiusSqr", mOuterRadiusSqr);
			pParams->setNamedConstant("fScale",mScale);
			pParams->setNamedConstant("fScaleOverScaleDepth",mScaleOverScaleDepth);
			
			pParams = pPass->getFragmentProgramParameters();

			pParams->setNamedConstant("fExposure",mExposure);
		}
		else
		{
			if(!mInAtmosphere) 
			{
				mInAtmosphere = true;
				toggle = true;
			}
			
			// ground from atmosphere
			pGround = mGroundFromAtmosphere;
			// sky from atmosphere
			pSky = mSkyFromAtmosphere;

			// sky
			pPass = pSky->getTechnique(0)->getPass(0);
			pParams = pPass->getVertexProgramParameters();
			
			pParams->setNamedConstant("fInnerRadius", mInnerRadius);
			pParams->setNamedConstant("fOuterRadius", mOuterRadius);
			pParams->setNamedConstant("fScale",mScale);
			pParams->setNamedConstant("fScaleOverScaleDepth",mScaleOverScaleDepth);
			
			pParams = pPass->getFragmentProgramParameters();

			pParams->setNamedConstant("fExposure",mExposure);
			
			// ground
			pPass = pGround->getTechnique(0)->getPass(1);
			pParams = pPass->getVertexProgramParameters();
			
			pParams->setNamedConstant("fInnerRadius", mInnerRadius);
			pParams->setNamedConstant("fOuterRadius", mOuterRadius);
			pParams->setNamedConstant("fScale",mScale);
			pParams->setNamedConstant("fScaleOverScaleDepth",mScaleOverScaleDepth);
			
			pParams = pPass->getFragmentProgramParameters();

			pParams->setNamedConstant("fExposure",mExposure);
		}

		
		if(toggle)
		{
			mParent->setGroundMaterial(pGround);
			for(unsigned int i = 0; i < 6; i++)
				mRenderable[i]->setMaterial(pSky->getName());
		}
	}
	
	void PlanetaryAtmosphere::setInAtmosphere(bool inAtmo)
	{
		mInAtmosphere = inAtmo;
	}
}
